如果你已經會 Vue、Nuxt,也碰過 React 或 Next,聽到「內容站可以用 Astro」,會先想問兩件事:這跟我已經會的框架到底差在哪?做 SEO 用 Nuxt 不就夠了?
部落格、文件站和行銷頁以內容為主,重點是文字、圖片,以及搜尋引擎讀得到的 HTML,適合用 Astro。互動多、還要跨頁共享狀態的網站,Nuxt 會更合適。接下來 30 天會用同一個內容站,讓你用實作回答三個問題:哪些頁面維持靜態、哪些區塊做成 island、哪些功能交給 server。
對照組有三個內容相同的頁面,用來比較 Astro 與 Nuxt 的 JavaScript 傳輸量:第 1 頁是 Astro 靜態頁;第 2 頁是 Astro 的 Vue
island,按鈕使用 client:load;第 3 頁是 Nuxt。
前兩頁的程式碼放在 app-steps。這個 repo 一天一個 commit,Day N 對應step-N,每篇文章末尾都會標出當天的 commit,所以你可以只看某一天改了哪幾行,不必從完整專案回推。Day 1 用的是step-01:
git clone https://github.com/stevecyj/app-steps.git
cd app-steps && git checkout step-01
npm ci && npm run build # 產出 dist/client
python3 -m http.server 4321 --directory dist/client
# 無痕開::4321/bench/(JS 0 個)、:4321/bench/island/(JS 3 個)
第 1 頁的路徑是 /bench,第 2 頁是/bench/island。用 Chrome 無痕模式開啟頁面,避免擴充套件注入腳本。接著在 DevTools 的 Network 面板選擇 JavaScript
filter,底部狀態列的 request/transferred 會顯示 JavaScript 傳輸量。
第 3 頁的 Nuxt 對照組不在那個 repo 裡,要自己建一個:執行 npm create nuxt@latest nuxt-bench(template 選
minimal),把 app/app.vue 換成跟前兩頁同一篇內容,並放入同樣的 <ReactionButton />。內容必須跟前兩頁逐字一致,量出來的差異才是框架造成的。建好後這樣量:
cd nuxt-bench && npm install && npm run generate # 產出 .output/public(是 generate 不是 build)
python3 -m http.server 4322 --directory .output/public
# 無痕開::4322/(JS 1 個)
在 Network 面板點 JS filter,會看到 1 個 chunk(整頁的 JS)。量測要用 generate 產出的正式版。npm run dev
會帶 HMR,而且沒有壓縮,量出的 JS 會比上線版本多。Nuxt 的 npm run build 會產出 Node 伺服器,.output/public
裡沒有 index.html,無法用靜態 serve 開啟頁面;Astro 的 build 才會直接產出靜態站。
表格中的 JS 大小是在 npm run build
後用無痕模式從 Network 讀取;FCP 是 Lighthouse 行動版各跑 3 次取中位數。raw 是 Network 顯示的未壓縮值,括號內是發佈上線的 gzip:
| 頁面 | JS 檔數 | JS 傳輸 raw / gzip | FCP |
|---|---|---|---|
| 第 1 頁 Astro 無島 | 0 | 0 B | 627 ms |
| 第 2 頁 Astro Vue 島 | 3 | 73.2 KB / ~29 KB | 921 ms |
| 第 3 頁 Nuxt | 1 | 129 KB / ~49 KB | 1353 ms |
Lighthouse 行動版 perf 在三頁都是 100。頁面很小,perf 分數看不出差異,但 FCP 看得出來。
第 1 頁 Astro 沒有 island:Network JS filter 顯示 0 個 script、0 kB。

第 2 頁 Astro Vue island:3 個 script。這頁原本只有 HTML 和 CSS,想讓按鈕能互動,就得再載入三個 JS:

第 3 頁 Nuxt:1 個 chunk、129 kB(整頁的 JS)。

(三張都是未壓縮的 raw 值,跟上表的 raw 欄一致;上線經 gzip 後約 0 / 29 / 49
KB。判讀時看底部狀態列「斜線前面」的數字。)
同一份內容、同一顆按鈕,在 Astro 無 island 時是 0 JS,Nuxt 則載入整頁的 JS。差異來自兩個框架的預設執行方式。
Astro 預設把每一頁當成一份印好的畫面:純 HTML 和 CSS 送到瀏覽器,不帶 JavaScript。需要互動的區塊才加上client:load,瀏覽器只會收到該區塊需要的 JS;未指定的地方維持零 JS。
Nuxt 預設把整頁交給 Vue 接管。頁面不論有沒有東西要按,瀏覽器開啟時都會先載入整頁的 Vue。因此在 Nuxt 裡放同一顆<ReactionButton />,不必加 client: 就能互動。
預設無 JS 對內容優先的網站有利:使用者更快看到能讀的東西,低速網路也撐得住,爬蟲取得的則是完整 HTML,對 SEO 有直接幫助。如果網站需要大量互動、跨畫面共享狀態,或 SPA 不換頁的過場,逐塊指定會限制實作方式,這類網站更適合由 Nuxt 接管整頁。
註:這段提到的預設行為,查證基準是 Astro 官方 islands 文件(Astro
v6)與 Nuxt 官方 rendering 文件(本文撰寫時為 Nuxt 4)。
照上面步驟量測,如果 Network 還是出現 JS,通常要先檢查量測環境。
本文的「0 JS」表示 Script 數量是 0。內容站仍會下載 HTML、圖片、favicon。因此 Network 面板要先點 JS filter,只看 script;底部的「X / Y
requests」是「篩選後 / 全部」,斜線前面的數字才是 JS 數量。
如果 JS filter 仍顯示一堆 JS,先排除以下兩種與「Astro 請求了多少 JS」無關的腳本來源。
1:在開發模式下量。用 npm run dev
啟動的網站不會是 0 JS。開發模式為了支援存檔後自動更新與開發工具列,會載入一批只在本機執行的 JS,上線版本不會有這些。量測時要看npm run build 產出的版本,不要看 dev。
2:使用一般瀏覽器,量到擴充套件的腳本。瀏覽器裝了擴充套件時,Network 可能多出幾支不是網站本身的 JS 檔,像detector.js、content_main.js、js.js、dom.js
。無痕模式預設停用擴充套件,能避免把這些檔案算進網站的 JS。
確認這兩件事(看 build 出來的產物、開無痕模式),再 Network 點 JS filter、看底部狀態列,數字才算得準。
這組量測有兩類數字可比較。JS 的結果是第 1 頁為 0,第 2 頁比第 3 頁少;Lighthouse 的 FCP 則是第 1 頁最快、第 2 頁其次、第 3 頁最慢。我這次量到的 gzip 是 0、29、49
KB,FCP 是 627、921、1353 ms,performance 分數則三頁都是 100。你量到的數字可能不同,順序應該接近。
只看 Lighthouse 的 100 不夠。小頁面很容易一排 100,但 FCP 還是會差一截。內容站的頁面越多、互動越少,少送一點 JS 的影響才會慢慢放大。反過來,如果你的站本來就很多互動、很多狀態要一起跑,只看這組 JS 和 FCP,還不夠讓你把 Nuxt 排除掉。
你的站如果明顯偏內容站,這次對照至少說明,同一份內容都用預設做法時,Astro 送出去的 JS 比較少,FCP 也比較快。
先比較 Vue /
Nuxt,兩者屬於同一條框架技術線。加入 React 和 Next 後,可以用同一篇內容、同一顆按鈕檢查這是不是 Vue 特例。React
/ Next 在這裡用來排除框架偏誤,系列仍以 Vue / Nuxt 為主。七種做法跑同一輪 Lighthouse(行動版、各 3 次取中位數):
| 做法 | 內容在 HTML | JS 檔數 | JS 傳輸 raw / gzip | FCP |
|---|---|---|---|---|
| Astro 無島 | 是 | 0 | 0 B | 627 ms |
| Astro Vue 島 | 是 | 3 | 73 KB / ~29 KB | 921 ms |
| Astro React 島 | 是 | 3 | 195 KB / ~62 KB | 1295 ms |
| Vue SPA | 否 | 1 | 62 KB / ~25 KB | 1051 ms |
| React SPA | 否 | 1 | 193 KB / ~61 KB | 1651 ms |
| Nuxt | 是 | 1 | 129 KB / ~49 KB | 1353 ms |
| Next | 是 | 6 | 514 KB / ~146 KB | 632 ms |
純 SPA 的 JS 其實最小,Vue SPA 才 25
KB,但內容不在 HTML。文章是 JavaScript 畫出來的,HTML 打開是空的,爬蟲和還沒跑完 JS 的人看到一片空白,FCP 也被拖到 1051
ms,比 Astro 無島的 627 ms 慢一截。內容站第一個要求就是內容要在 HTML 裡。
把能達到 SEO 效果的做法放在一起比較(Astro 島、Nuxt、Next),Astro 把 JS 綁在島上,沒島的頁就是 0;Nuxt 和 Next 不管有沒有要互動,都先送一份整頁的框架。Vue 與 React 都有相同結果。
把內容能出現在 HTML 的幾種做法抽出來,照 JS 由小到大排:
| 做法 | JS 檔數 | JS 傳輸 raw / gzip | FCP |
|---|---|---|---|
| Astro 無島 | 0 | 0 B | 627 ms |
| Astro Vue 島 | 3 | 73 KB / ~29 KB | 921 ms |
| Nuxt | 1 | 129 KB / ~49 KB | 1353 ms |
| Astro React 島 | 3 | 195 KB / ~62 KB | 1295 ms |
| Next | 6 | 514 KB / ~146 KB | 632 ms |
最小的兩個都是 Astro,沒 island 的頁面是 0。
內容是否在 HTML,取決於框架的使用方式。
我量過一個上線中的多語系品牌官網,它用的就是 Astro。它每一頁都是一個 .astro 外殼,裡面掛幾個 Vue 元件、全部加client:load,文案和圖片都寫在那些 Vue 元件裡。build 出來,首頁 HTML 只有 7.1 KB,首頁要載的 JS 是 210 KB。
這個 Astro 專案的結果對應上表 Vue
SPA 那一格,內容不在 HTML,要等 JS 跑完才出現。團隊原本使用 Vue,把整個 view 掛上來需要的改動最少。要拿到前面量到的好處,還得判斷哪些內容不必是 JS,光是選擇 Astro 並不夠。
要拿到上面那組數字,必須只讓真正需要互動的區塊載入 JS。
內容優先的網站可以把 Astro 納入選項,並先訂出一個驗收標準:文章頁預設輸出完整 HTML、0
JS;只有真正需要互動的元件才變成 island。建立 Astro 專案後,就能用這項標準逐頁檢查。
本日程式碼:step-01